package externalSort.Tournament;

import externalSort.fileNameGenerator;
import externalSort.filePair;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;

public class mergeFileByHuffman {
    public List<filePair> generations;
    public int bufferSize;
    private final String outPutDir;
    public int k;
    private List<BlockingQueue<bufferSimulator>> blockingQueues;
    private final tournamentTree tree;

    private bufferReadManager reader;
    private bufferWriteManager writer;

    // 记录k way中每一路是否已经读入完毕
    private boolean[] finished;
    private List<bufferSimulator> currentBuffer;

    public mergeFileByHuffman(List<filePair> generations,int bufferSize,String outPutDir,int k){
        this.generations=generations;
        this.bufferSize=bufferSize;
        this.outPutDir=outPutDir;
        this.k=k;
        tree=new tournamentTree(k);

        finished=new boolean[k];
        for(int i=0;i<k;i++){
            finished[i]=false;
        }

        this.reader=new bufferReadManager(bufferSize,k);
        this.writer=new bufferWriteManager(bufferSize);

        this.currentBuffer = new ArrayList<>(Collections.nCopies(k, (bufferSimulator) null));
    }

    public List<List<filePair>> huffman() throws Exception {
        PriorityQueue<filePair> pq=new PriorityQueue<>(Comparator.comparingInt(a -> a.cnt));
        pq.addAll(generations);
        List<List<filePair>> list=new ArrayList<>();
        List<filePair> cur=new ArrayList<>();

        // 填充空顺串
        int fc=generations.size();

        while(k>2&&(fc-1)%(k-1)!=0){
            String curFileName="./file/generation/run_" + fc++ + ".txt";
            File file=new File(curFileName);
            file.createNewFile();
            generations.add(new filePair(curFileName,0));
        }

        while(pq.size()>1){
            List<filePair> strs=new ArrayList<>(Math.min(k,pq.size()));

            for(int i=0;i<k&&!pq.isEmpty();i++){
                filePair fp=pq.poll();
                cur.add(fp);
                strs.add(fp);
            }

            // 归并
            filePair ret=merge2(cur);

//            System.out.println("[huffman] AT huffman : 得到的新的buffer大小为 "+ret.cnt);

            pq.add(ret);
            list.add(strs);

            //剔除中间文件
            for(filePair fp:cur){
                String name=fp.name;
                new java.io.File(name).delete();
            }

            cur.clear();
        }

        reader.executorService.shutdown();

        return list;
    }

    private void initTree(int sz) throws Exception{
        int[] initVals=new int[sz];
        for(int i=0;i<sz;i++){
            currentBuffer.set(i,reader.pollBuffer(i));
//            System.out.println("[huffman] AT initTree : way " + i + " 成功获得一个buffer");
            initVals[i]=currentBuffer.get(i).poll();
            if(currentBuffer.isEmpty()){
                finished[i]=true;
            }
//            System.out.println("[huffman] AT initTree : way " + i + " 初始值为 " + initVals[i]);
        }
        tree.initVals(initVals);
    }

    private filePair merge2(List<filePair> files) throws Exception {
        List<BufferedReader> bufferedReaders=initBufferReaders(files);

        String fileName=fileNameGenerator.generateMergeFileName(files,this.outPutDir);
        BufferedWriter bw=new BufferedWriter(new FileWriter(fileName));
        int fileCnt=0;

        /*
        2 个写入buffer
        2k 个读buffer
         */

        /*
        读入的buffer状态其实就是IDLE <-> READING <-> ACTIVE
        只需要在这里进行Reader初始化，从队列中取出对应的buffer，然后败者树内存操作
        write函数具体实现不需要管
         */

        reader.setBufferedReaders(bufferedReaders);
        writer.setBufferedWriter(bw);

        Thread readThread=new Thread(reader);
        Thread writeThread=new Thread(writer);

        writeThread.start();
        readThread.start();

        for(int i=0;i<k;i++){
            finished[i]=false;
        }

        initTree(bufferedReaders.size()); // 初始化败者树

        while(true){
            int curWay=tree.getLoserWay();
//            System.out.println("[huffman] AT merge2 : 败者way"+ curWay);

            // 1. 从需要读入的way中取出来一个数
            int ready=0;
            // 如果已经读完了，那就是INT_MAX
            if(finished[curWay]||curWay>=files.size()){
                ready=Integer.MAX_VALUE;
//                System.out.println("[huffman] AT merge2 : ready1="+ ready);
            }

            // 如果这个buffer读完了，更新
            else{
                if(currentBuffer.get(curWay).isEmpty()) {
//                    System.out.println("[huffman] AT merge2 : buffer用完，释放");
                    reader.releaseBuffer(currentBuffer.get(curWay));
                    currentBuffer.set(curWay, reader.pollBuffer(curWay));
                }
                ready=currentBuffer.get(curWay).poll();
                if(ready==Integer.MAX_VALUE){
                    finished[curWay]=true; // 读到终止符了，就标定这个way读完了
                    reader.releaseBuffer(currentBuffer.get(curWay));
                }
            }

//            System.out.println("[huffman] AT merge2 : ready2="+ ready);

            // 2. 执行替换操作
            int moveOutNum=tree.moveOut(ready,ready==Integer.MAX_VALUE);

//            System.out.println("[huffman] AT merge2 : moveOutNum=" + moveOutNum);


            ++fileCnt;
            writer.write(moveOutNum);
            // 3. 写回
            if(moveOutNum==Integer.MAX_VALUE){
                break;
            }
        }

//        readThread.join();
        writer.write(Integer.MAX_VALUE);
        writeThread.join();
//        reader.close();
//        writer.close();

        return new filePair(fileName,fileCnt);
    }

    private filePair merge1(List<filePair> files) throws Exception {
        int n=files.size();
        List<BufferedReader> bufferedReaders=initBufferReaders(files);

        String fileName=fileNameGenerator.generateMergeFileName(files,this.outPutDir);

        int fileCnt=0;

        // init writer
        try(BufferedWriter bw=new BufferedWriter(new FileWriter(fileName))){
            boolean allEmpty=false;
            String s=null;

            // init 先把每个数都拿出来一个
            List<int[]> buffers=new ArrayList<>();
            for(int i=0;i<n;i++){
                BufferedReader br=bufferedReaders.get(i);
                int val=((s=br.readLine())!=null)?Integer.parseInt(s.trim()):Integer.MAX_VALUE;
                buffers.add(new int[]{i,val});
            }

            while(true){
                int curWay=getMinValWay(buffers);
                if(curWay==-1){
                    break;
                }
                int[] minBuffer=buffers.get(curWay);
                int val=minBuffer[1];
                if(val==Integer.MAX_VALUE){
                    break;
                }
                bw.write(val +"\n");// 先写入上一个值
                fileCnt++;
                BufferedReader br=bufferedReaders.get(curWay);
                minBuffer[1]=((s=br.readLine())!=null)?Integer.parseInt(s.trim()):Integer.MAX_VALUE;// 终止
            }
        }finally {
            for (BufferedReader br:bufferedReaders) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.err.println("关闭文件时出错: " + e.getMessage());
                }
            }
        }
//        Thread.sleep(2);
        return new filePair(fileName,fileCnt);
    }

    // i -> val
    private int getMinValWay(List<int[]> nums){
        int ret=-1,mn=Integer.MAX_VALUE;
        // 这里先试用线性比较
        // 等到Phase4再使用loserTree
        for (int[] num : nums) {
            if (ret == -1 || num[1] < mn) {
                mn = num[1];
                ret = num[0];
            }
        }
        return ret;
    }

    private List<BufferedReader> initBufferReaders(List<filePair> files) throws Exception {
        List<BufferedReader> bufferedReaders=new ArrayList<>();
        for (filePair file : files) {
            try {
                BufferedReader br = new BufferedReader(new FileReader(file.name), this.bufferSize);
                bufferedReaders.add(br);
            } catch (IOException e) {
                System.out.println("[mergeFileByHuffman] merge1: IOException Occurs:" + e.getMessage());
                throw e;
            }
        }
        return bufferedReaders;
    }
}
